using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Text;
using System.Web;

namespace Common
{
    public class MSNConnection
    {
        // These are the numbers where the addresses wil appear in the arraylist
        // it is not the niced solution, but it works a lot faster
        // you can use some kind of IDictionary object to put the value behind a key name
        //
        // PASPORTURLS
        private int DARREALM = 0;
        private int DALOGIN = 1;
        private int DAREG = 2;
        private int PROPERTIES = 3;
        private int GENERALDIR = 4;
        private int HELP = 5;
        private int CONFIGVERSION = 6;

        private ArrayList PassportUrls;

        /**/
        /// <summary>
        /// Every message from a client has a unique transactionID
        /// With every message this number will be increased
        /// </summary>
        private long _transactionID = 0;

        private TcpClient _socket;
        private NetworkStream _stream;
        private StreamReader _reader;
        private StreamWriter _writer;
        private Dictionary<string, string> _linkmans;
        protected string _UserName;
        protected string _ScreenName;
        private string _msnNickname;
        protected bool DataAvailable
        {
            get { return _stream.DataAvailable; }
        }

        protected bool Connected
        {
            get { return _socket != null; }
        }

        public Dictionary<string, string> Linkmans
        {
            get { return _linkmans; }
        }
        public string MsnNickname
        {
            get { return _msnNickname; }
        }

        public MSNConnection()
        {
            _linkmans = new Dictionary<string, string>();
            _msnNickname = "";
        }
        /**/
        /// <summary>
        /// Make a socket connection and streamers
        /// </summary>
        /// <param name="host">IP host to connect to</param>
        /// <param name="port">IP port to connect to</param>
        protected void ConnectSocket(string host, int port)
        {
            //Console.WriteLine("Connecting to " + host + ":" + port);
            //_transactionID = 0;
            _socket = new TcpClient(host, port);
            _stream = _socket.GetStream();
            //_reader = new StreamReader(_stream, Encoding.ASCII);
            //_writer = new StreamWriter(_stream, Encoding.ASCII);
            //_writer.AutoFlush = true;
        }

        /**/
        /// <summary>
        /// Write a message to the current socket, this functions raises the transactionID
        /// </summary>
        /// <param name="line">The Message</param>
        /// <param name="writeNewLine">Determine if you write a line, or only a message (with or withhout ending character</param>
        private void WriteLine(string line, bool writeNewLine)
        {
            //Console.WriteLine("Writing: " + line);
            if (_stream.CanWrite)
            {
                if (writeNewLine)
                    line += "\r\n";
                byte[] myWriteBuffer = Encoding.ASCII.GetBytes(line);
                _stream.Write(myWriteBuffer, 0, myWriteBuffer.Length);
            }

            /**/
            /*         
                  if (writeNewLine)
                     _stream.Write(
                     _writer.WriteLine(line);
                 else
                     _writer.Write(line);
         */
            // raise the transactionId
            _transactionID++;
        }

        /**/
        /// <summary>
        /// Write a command to the server
        /// </summary>
        /// <param name="command">The first command</param>
        /// <param name="parameters">The parameters that have to be send to the server</param>
        /// <param name="bSendId">Sometimes you don't have to send an transactionID</param>
        protected void WriteCommand(string command, string parameters, bool bSendId)
        {
            string line;
            // check what type of format it should be
            if (bSendId)
                line = string.Format("{0} {1} {2}", command, _transactionID, parameters);
            else
                line = string.Format("{0} {1}", command, parameters);
            // Write the line
            WriteLine(line, true);
        }

        /**/
        /// <summary>
        /// This function read the information from the streamreader
        /// and if it read something it returns a new ServerCommand object
        /// </summary>
        /// <returns>if there is something return new ServerCommand object</returns>
        protected ServerCommand ReadCommand()
        {
            string line = string.Empty;
            if (_stream.CanRead)
            {
                byte[] myReadBuffer = new byte[1024];
                StringBuilder myCompleteMessage = new StringBuilder();
                int numberOfBytesRead = 0;

                // Incoming message may be larger than the buffer size.
                do
                {
                    numberOfBytesRead = _stream.Read(myReadBuffer, 0, myReadBuffer.Length);

                    myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(myReadBuffer, 0, numberOfBytesRead));

                }
                while (_stream.DataAvailable);
                line = myCompleteMessage.ToString();
            }



            //string line = _reader.ReadLine();
            //Console.WriteLine("Reading: " + line);
            if (line == null)
            {
                //Console.WriteLine("Nothing received");
                return new ServerCommand();
            }
            else
            {
                return new ServerCommand(line);
            }
        }

        /**/
        /// <summary>
        /// This function closes the connection, if its open
        /// </summary>
        protected void Dispose()
        {
            if (_socket != null)
            {
                //_reader.Close();
                //_writer.Close();
                _stream.Close();
                _socket.Close();
                _socket = null;
            }
        }

        public int Connect(string UserName, string UserPassword)
        {
            string host = "messenger.hotmail.com";
            int port = 1863;
            string ChallengeString;
            ServerCommand ServCom;

            try
            {
                while (true)
                {
                    ConnectSocket(host, port);

                    // Login and provide the current version you want to use
                    WriteCommand("VER", "MSNP9 CVRO", true);
                    ServCom = ReadCommand();

                    // check return value
                    if (ServCom.CommandName != "VER")
                    {
                        return 1;
                    }

                    // act like a real msn client and send some information
                    WriteCommand("CVR", "0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS " + UserName, true);
                    ServCom = ReadCommand();

                    // check return value
                    if (ServCom.CommandName != "CVR")
                    {
                        return 1;
                    }

                    // This indicates that you want to connect this user
                    WriteCommand("USR", "TWN I " + UserName, true);
                    ServCom = ReadCommand();

                    // If the server provided the USR answer, than you have got a challenges
                    // You need this challenge to get a valid clienticket
                    if (ServCom.CommandName == "USR")
                    {
                        ChallengeString = ServCom.Param(3);
                        break;
                    }

                    // if the command is nog XFR, then whe don't know what to do anymore
                    if (ServCom.CommandName != "XFR")
                    {
                        return 1;
                    }
                    else
                    {
                        //Console.WriteLine("Redirected to other server");
                    }

                    // If we get here, then we have to change servers, get the IP number
                    string[] arIP = ServCom.Param(2).Split(':');
                    host = arIP[0];
                    // Also get the portnumber 
                    port = int.Parse(arIP[1]);

                    // close the current connection
                    Dispose();
                }

                // Get a valid ticket 
                string clientticket = GetClientTicket(UserPassword, UserName, ChallengeString);

                if (clientticket == "401")
                {
                    // if the code is 401, then most likely there was a error in 
                    // your username password combination
                    return 401;
                }
                else if (clientticket == "0")
                {
                    // unknown error occured
                    return 1;
                }
                else
                {
                    // finnaly send a valid ticket ID back to the server
                    WriteCommand("USR", "TWN S " + clientticket, true);
                    ServCom = ReadCommand();

                    // if the response is USR <trans> OK then we are succesfully connected
                    if (ServCom.CommandName != "USR" && ServCom.Param(1) != "OK")
                    {
                        return 1;
                    }
                    _msnNickname = ServCom.Param(3);//MsnNickName
                    //ServCom(2);//email
                    //_transactionID = 8;

                    // retrieve the correct username (is always the same, but who cares)
                    // and retrieve you screenname that you have provided last time
                    //_UserName = ServCom.Param(2);
                    //_ScreenName = ServCom.Param(3);

                    //WriteCommand("LST", "FL", true);
                    //ServCom = ReadCommand();

                    WriteCommand("SYN", "0", true);
                    System.Threading.Thread.Sleep(2000);
                    ServCom = ReadCommand();
                    GetLinkmans(ServCom);
                    // Change the status in online, so can other see you come online
                    //WriteCommand("CHG", "NLN", true);
                    //ServCom = ReadCommand();
                    Dispose();
                    return 0;
                }
            }
            catch (Exception ex)
            {
                //Console.WriteLine("Unknown error occured errocode: " + ex.Message);
                return 1;
            }
        }

        /**/
        /// <summary>
        /// This function asks a valid login adres, to connect to
        /// </summary>
        /// <returns>true if succeed</returns>
        protected bool GetLoginServerAddres()
        {
            // Make a request to the server, this adresses are being used in the MSN messenger
            HttpWebRequest ServerRequest = (HttpWebRequest)WebRequest.Create("https://nexus.passport.com/rdr/pprdr.asp");
            // Get the result
            HttpWebResponse ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();
            if (ServerResponse.StatusCode == HttpStatusCode.OK)
            {
                string retrieveddata = ServerResponse.Headers.ToString();
                PassportUrls = new ArrayList();
                // Pick up the header en split
                string[] result = ServerResponse.Headers.Get("PassportURLs").Split(',');
                foreach (string s in result)
                {
                    // The actual adres is provided behind the '=' sign
                    PassportUrls.Add(s.Substring(s.IndexOf('=') + 1));
                }
                ServerResponse.Close();
                return true;
            }
            else
            {
                ServerResponse.Close();
                return false;
            }
        }


        /**/
        /// <summary>
        /// This function connects to a login server to request a valid ticket,
        /// that will be used to login on the MSN servers
        /// </summary>
        /// <param name="Password">The password of the user, this is just plain text. The connection is HTTPS
        /// <param name="Username">The complete username</param>
        /// <param name="ChallengeString">A challenge string that you have got, wile connecting to a msn server
        /// <returns>a valid ticket, that you send back to the server to get connected</returns>
        protected string GetClientTicket(string Password, string Username, string ChallengeString)
        {
            // First get a valid login adres for the initial server
            if (GetLoginServerAddres())
            {
                // On the position of DALOGIN is a valid URL, for login
                string uri = "https://" + PassportUrls[DALOGIN];

                HttpWebRequest ServerRequest;
                HttpWebResponse ServerResponse;
                try
                {
                    while (true)
                    {

                        //Console.WriteLine("Connecting to:  " + uri);
                        // Make a new request
                        ServerRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
                        ServerRequest.AllowAutoRedirect = false;
                        ServerRequest.Pipelined = false;
                        ServerRequest.KeepAlive = false;
                        ServerRequest.ProtocolVersion = new Version(1, 0);
                        // Send the authentication header
                        ServerRequest.Headers.Add("Authorization", "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=" + Username.Replace("@", "%40") + ",pwd=" + Password + "," + ChallengeString + "\n");
                        // Pick up the result
                        ServerResponse = (HttpWebResponse)ServerRequest.GetResponse();

                        // If the statuscode is OK, then there is a valid return
                        if (ServerResponse.StatusCode == HttpStatusCode.OK)
                        {
                            // Pick up the information of the authentications
                            string AuthenticationInfo = ServerResponse.Headers.Get("Authentication-Info");
                            // Get the startposition of the ticket id (note it is between two quotes)
                            int startposition = AuthenticationInfo.IndexOf('\'');
                            // Get the endposition 
                            int endposition = AuthenticationInfo.LastIndexOf('\'');
                            // Substract the startposition of the endposition
                            endposition = endposition - startposition;

                            // Close connection
                            ServerResponse.Close();

                            // Generate a new substring and return it
                            return AuthenticationInfo.Substring(startposition + 1, endposition - 1);

                        }
                        // If the statuscode is 302, then there is a redirect, read the new adres en connect again
                        else if (ServerResponse.StatusCode == HttpStatusCode.Found)
                        {
                            uri = ServerResponse.Headers.Get("Location");
                        }
                    }
                }
                catch (WebException e)
                {
                    // If the statuscode is 401, then an exeption occurs
                    // Think that your password + username combination is not correct
                    // return number so that calling functions knows what to do
                    if (e.Status == WebExceptionStatus.ProtocolError)
                    {
                        return "401";
                    }
                    else
                    {
                        return "0";
                    }
                }
            }
            return "0";
        }
        /**/
        /// <summary>
        /// parse linkman
        /// </summary>
        /// <param name="line"></param>
        private void GetLinkmans(ServerCommand servCommand)
        {
            _linkmans.Clear();
            string _line = servCommand.Line;
            string[] a = _line.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string s in a)
            {
                string[] l = s.Split(' ');
                if (l.Length == 5 && l[0] == "LST")
                {
                    _linkmans.Add(l[1], l[2]);
                }
            }
        }
    }

    /**/
    /// <summary>
    /// This class is uses, to use the result of the server in a easy way
    /// </summary>
    public class ServerCommand
    {
        // The command name of the response
        private string _cmdID;
        // The entire response
        private string _line;
        // The parameters of the response (without transactionID, and divided with a space)
        private string[] _params;

        /**/
        /// <summary>
        /// Constructor for parsing the result line from the streamreader
        /// </summary>
        /// <param name="line"></param>
        public ServerCommand(string line)
        {
            _line = line;
            // always 3 characters command
            _cmdID = line.Substring(0, 3);
            if (!(_cmdID == "QNG"))
            {
                _params = line.Substring(4).Split(' ');
            }
        }

        /**/
        /// <summary>
        /// This constructor makes a valid object
        /// but the command name idicates that there was something wrong
        /// </summary>
        public ServerCommand()
        {
            _line = "";
            _cmdID = "ERROR";
        }

        public string CommandName
        {
            get { return _cmdID; }
        }

        public string Param(int index)
        {
            return _params[index];
        }

        public int ParamCount
        {
            get { return _params.Length; }
        }

        public string Line
        {
            get { return _line; }
        }
    }
}
